using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Data;
using System.Xml;

using NHibernate.Criterion;

using Pampa.CRUD.Core;
using Pampa.CRUD.Exceptions;

using Pampa.Grid.Core;
using Pampa.Grid.Core.Filters;
using Pampa.Grid.Exceptions;

namespace Pampa.Grid.Data
{
    public abstract class GridDataReaderEntityBase : GridDataReader
    {
        protected string entityName;
        protected object[] entities;
        protected int cursor;

        protected GridDataReaderEntityBase() : base()
        {
        }

        protected GridDataReaderEntityBase(string entityName) : base()
        {
            this.entityName = entityName;
        }
        
        #region Properties

        public string EntityName
        {
            get { return entityName; }
        }

        private object CurrentEntity
        {
            get { return this.entities[this.cursor]; }
        }
        
        #endregion

        #region Public Funtions

        public override sealed void Open(Configuration configuration, int pageSize, int pageNumber)
        {
            this.cursor = -1;
            GetEntities(configuration, pageSize, pageNumber);
        }

        public override int GetCount(Configuration configuration)
        {
            ICriterion[] arrCriterias = GetCriterias(configuration);
            return CountEntities(arrCriterias);
        }

        public override bool Read()
        {
            this.cursor++;
            return (this.cursor < entities.Length);
        }

        public override object GetValue(string fieldName)
        {
            try
            {
                return EntityManager.GetPropertyValue(this.CurrentEntity, fieldName);
            }
            catch(PropertyNotFoundException) 
            { 
                throw (new DataReaderException("There is no field defined with name '" + fieldName + "'"));
            }
        }

        public override void Close()
        {
            this.cursor = -1;
            this.entities = null;
        }

        #endregion

        #region Xml Functions

        public override void LoadXml(System.Xml.XmlNode node)
        {
            XmlNamespaceManager nsManager = new XmlNamespaceManager(node.OwnerDocument.NameTable);
            nsManager.AddNamespace("pampa", node.NamespaceURI);

            XmlNode nodeDataSource = node.SelectSingleNode("pampa:DataSource", nsManager);
            if (nodeDataSource == null)
                throw (new GridConfigurationException("Node 'DataSource' was not found into the node 'DataReader'."));

            if (nodeDataSource.Attributes["Name"] == null)
                throw (new GridConfigurationException("Attribute 'Name' can not be found in node 'DataSource'."));

            this.entityName = nodeDataSource.Attributes["Name"].Value.ToString();
        }

        public override void ToXml(ref System.Xml.XmlNode root)
        {
            XmlDocument document = root.OwnerDocument;
            XmlNode nodeDataReader = document.CreateElement("DataReader");
            root.AppendChild(nodeDataReader);

            XmlAttribute attribute;
            attribute = document.CreateAttribute("Type");
            attribute.Value = this.GetType().FullName;
            nodeDataReader.Attributes.Append(attribute);

            XmlNode nodeDataSource = document.CreateElement("DataSource");
            attribute = document.CreateAttribute("EntityName");
            attribute.Value = this.EntityName;
            nodeDataSource.Attributes.Append(attribute);

            nodeDataReader.AppendChild(nodeDataSource);
        }

        #endregion

        #region Protected Functions

        protected virtual void GetEntities(Configuration configuration, int pageSize, int pageNumber)
        {
            Order[] arrOrders = GetOrders(configuration.OrderBy);
            ICriterion[] arrCriterias = GetCriterias(configuration);

            // Get Entities
            if (pageSize == 0)
            {
                entities = GetEntities(arrOrders, arrCriterias);
            }
            else
            {
                int firstResult = pageSize * (pageNumber - 1);
                int maxResult = pageSize;
                entities = GetEntities(firstResult, maxResult, arrOrders, arrCriterias);
            }
        }

        protected virtual object[] GetEntities(Order[] order, params ICriterion[] criteria)
        {
            return new object[] {};
        }

        protected virtual object[] GetEntities(int firstResult, int maxResult, Order[] order, params ICriterion[] criteria)
        {
            return new object[] { };
        }

        protected virtual int CountEntities(params ICriterion[] criteria)
        {
            return 0;
        }
        
        protected virtual Order[] GetOrders(List<KeyValuePair<string, OrderDirection>> orderBy)
        {
            Order[] arrOrders = new Order[orderBy.Count];
            int i = 0;

            foreach (KeyValuePair<string, OrderDirection> configOrder in orderBy)
            {
                Order order = new Order(configOrder.Key, (configOrder.Value == OrderDirection.Ascending));
                arrOrders[i] = order;
                i++;
            }

            return arrOrders;
        }

        protected virtual ICriterion[] GetCriterias(Configuration configuration)
        {
            IList<ICriterion> criteria = new List<ICriterion>();
            ICriterion staticFilterCriteria = GetCriteria(configuration.StaticFilter);
            ICriterion mainFilterCriteria = GetCriteria(configuration.MainFilter);
            ICriterion quickFilterCriteria = GetCriteria(configuration.QuickFilter);

            if (staticFilterCriteria != null)
                criteria.Add(staticFilterCriteria);

            if (mainFilterCriteria != null)
                criteria.Add(mainFilterCriteria);

            if (quickFilterCriteria != null)
                criteria.Add(quickFilterCriteria);

            ICriterion[] arrCriteria = new ICriterion[criteria.Count];
            criteria.CopyTo(arrCriteria, 0);

            return arrCriteria;
        }

        protected virtual ICriterion GetCriteria(GroupFilter parentFilter)
        {
            ICriterion criteria = null;
            ICriterion fullCriteria = null;

            foreach (Filter filter in parentFilter.InnerFilters)
            {
                if (filter is SimpleFilter)
                    criteria = GetCriteriaFromSimpleFilter((SimpleFilter)filter);
                else if (filter is GroupFilter)
                    criteria = GetCriteria((GroupFilter)filter);
                else
                    throw (new DataReaderException("Filter not supported [" + filter.GetType().Name + "]"));

                if (criteria != null)
                {
                    if (fullCriteria == null)
                        fullCriteria = criteria;
                    else if (parentFilter is AndGroupFilter)
                        fullCriteria = Expression.And(fullCriteria, criteria);
                    else if (parentFilter is OrGroupFilter)
                        fullCriteria = Expression.Or(fullCriteria, criteria);
                }
            }

            return fullCriteria;
        }

        protected virtual ICriterion GetCriteriaFromSimpleFilter(SimpleFilter simpleFilter)
        {
            ICriterion criteria = null;

            switch (simpleFilter.Operation)
            {
                case OperationType.Equal:
                    criteria = Expression.Eq(simpleFilter.Field, simpleFilter.Value);
                    break;
                case OperationType.Grater:
                    criteria = Expression.Gt(simpleFilter.Field, simpleFilter.Value);
                    break;
                case OperationType.GraterEqual:
                    criteria = Expression.Ge(simpleFilter.Field, simpleFilter.Value);
                    break;
                case OperationType.Less:
                    criteria = Expression.Lt(simpleFilter.Field, simpleFilter.Value);
                    break;
                case OperationType.LessEqual:
                    criteria = Expression.Le(simpleFilter.Field, simpleFilter.Value);
                    break;
                case OperationType.NotEqual:
                    criteria = Expression.Not(Expression.Eq(simpleFilter.Field, simpleFilter.Value));
                    break;
                case OperationType.StartsWith:
                    criteria = Expression.Like(simpleFilter.Field, simpleFilter.Value.ToString(), MatchMode.Start);
                    break;
                case OperationType.Contains:
                    criteria = Expression.Like(simpleFilter.Field, simpleFilter.Value.ToString(), MatchMode.Anywhere);
                    break;
                case OperationType.EndsWith:
                    criteria = Expression.Like(simpleFilter.Field, simpleFilter.Value.ToString(), MatchMode.End);
                    break;
            }

            return criteria;
        }

        #endregion
    }
}
